//! Handles input buffering between the render thread and the tick thread.
//!
//! We can't use a standard channel because that would behave weirdly.

use std::mem;
use std::sync::atomic::{AtomicUsize, Ordering};

use parking_lot::{Mutex, MutexGuard};

use craftdig_datamodel::block::action;
use craftdig_math::*;

/// Ring of input buffers to accept inputs into buffers, allowing the tick
/// thread to take out ready buffers when it decides to.
pub struct InputRing {
    buffers: Vec<Mutex<ClientInputBuffer>>,
    tick_idx: AtomicUsize,
}

impl InputRing {
    pub fn new(cnt: usize) -> Self {
        let buffers = (0..cnt)
            .map(|_| Mutex::new(ClientInputBuffer::default()))
            .collect::<_>();
        Self {
            buffers,
            tick_idx: AtomicUsize::new(0),
        }
    }

    fn get_buffer_idx(&self, idx: usize) -> &Mutex<ClientInputBuffer> {
        &self.buffers[idx % self.buffers.len()]
    }

    // FIXME I think this locking design doesn't actually work as expected

    /// Returns a guard to the current input buffer.
    pub fn lock_cur_buffer(&self) -> MutexGuard<'_, ClientInputBuffer> {
        let cur_idx = self.tick_idx.load(Ordering::SeqCst);
        let buf_lock = self.get_buffer_idx(cur_idx);
        buf_lock.lock()
    }

    /// Takes an input buffer from the ring, replacing it with a fresh one.
    pub fn take_input(&self) -> ClientInputBuffer {
        let idx = self.tick_idx.fetch_add(1, Ordering::SeqCst);
        let buf_lock = self.get_buffer_idx(idx);
        let mut buf_g = buf_lock.lock();
        let mut buf = ClientInputBuffer::default();
        mem::swap(&mut buf, &mut buf_g);
        buf
    }
}

/// Represents buffered inputs getting ready for the next tick, from the
/// player's inputs.
#[derive(Clone, Debug, Default)]
pub struct ClientInputBuffer {
    new_position: Option<Vec3f>,
    jumped: bool,
    actions: Vec<action::Action>,
}

impl ClientInputBuffer {
    pub fn new_position(&self) -> Option<Vec3f> {
        self.new_position
    }

    pub fn is_jumped(&self) -> bool {
        self.jumped
    }

    pub fn actions(&self) -> &[action::Action] {
        &self.actions
    }

    pub fn set_player_position(&mut self, pos: Vec3f) {
        self.new_position = Some(pos)
    }

    pub fn set_player_jumped(&mut self) {
        self.jumped = true;
    }

    pub fn add_player_action(&mut self, a: action::Action) {
        self.actions.push(a);
    }
}
